class Vue {
  constructor(options) {
    // 保存配置项
    this.$options = options || {};
    this.$data = options.data || {};
    this.$methods = options.methods || {};
    this.$el =
      typeof options.el === "string"
        ? document.querySelector(options.el)
        : options.el;
    // 将 data 转换为 getter/setter， 注入 Vue 实例
    this._proxyData(this.$data);
    this._proxyMethods(this.$methods);

    new Observer(this.$data);

    new Compiler(this)
  }

  _proxyMethods(methods){
    Object.keys(methods).forEach(method => {
      this[method] = typeof methods[method] === 'function' ? methods[method].bind(this) : () => {}
    })
  }

  _proxyData(data) {
    // 遍历 data 中的数据，将其一一绑定在 Vue 实例上
    Object.keys(data).forEach((prop) => {
      // 这里的 this 即 Vue 这个类
      Object.defineProperty(this, prop, {
        enumerable: true,
        configurable: true,
        get() {
          return data[prop];
        },
        set(value) {
          if (value === data[prop]) {
            return;
          }
          data[prop] = value;
        },
      });
    });
  }
}

// 通过 Observer 将 data 中的数据转换为响应式数据
// 深度遍历 data， 使其每一个值都变成响应式数据
// 当数据发生变化时，发出对应的通知
class Observer {
  constructor(data) {
    this.walk(data);
  }
  walk(data) {
    if (!data || typeof data !== "object") {
      return;
    }
    Object.keys(data).forEach((key) => {
      this.defineReactive(data, key, data[key]);
    });
  }
  defineReactive(obj, key, value) {
    // 如果 value 是一个引用值，如 person: {name: 'zs'}
    // 继续调用 walk 方法，将里面的值变成响应式的
    // 如果 value 是一个原始值，walk 中的判断会生效，return 不做任何处理
    // 这样，引用值就有了响应式特性
    const _this = this;
    const dep = new Dep()  
    this.walk(value);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        Dep.target && dep.addSub(Dep.target)
        return value;
      },
      set(newValue) {
        if (newValue === value) return;
        value = newValue;
        // 重新赋值之后，可能出现原来是原始值，是响应式的
        // 赋值为一个 object，失去了响应式，这时需要对新赋值再进行一次 defineReactive
        _this.walk(value);
        dep.notify()
      },
    });
  }
}
